﻿/*
 *********************************************************************
 *    Management Extensions for Windows Windows Server AppFabric     *
 *********************************************************************
 *    Author    : Fatih Boy [fatih@enterprisecoding.com]             *
 *    Web site  : http://www.enterprisecoding.com                    *
 *********************************************************************
 *    License   : GNU Library General Public License (LGPL)          * 
 *********************************************************************
 */

using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using Com.Enterprisecoding.AppFabric.Extensions.Client.ObjectPicker.Interop;

namespace Com.Enterprisecoding.AppFabric.Extensions.Client.ObjectPicker {
    public sealed class ObjectPicker {
        #region Data Members
        private const int NUMBER_OF_SCOPES = 5;
        private DialogResult dialogResult;
        private List<ADEntry> adEntries = new List<ADEntry>();
        #endregion

        #region Properties
        public ADEntry[] Selections { get { return adEntries.ToArray(); } }
        #endregion

        private void Initialize(DSOP_SCOPE_INIT_INFO[] scopeInitInfo) {
            var size = (uint)Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO));

            scopeInitInfo[0].cbSize = size;
            scopeInitInfo[0].flType = (uint)DSOP_SCOPE_TYPE.TargetComputer;
            scopeInitInfo[0].flScope = (uint)DSOP_SCOPE_FLAG.StartingScope;
            scopeInitInfo[0].FilterFlags.Uplevel.flBothModes = (uint)DSOP_FILTER.Users;
            scopeInitInfo[0].FilterFlags.flDownlevel = (uint)(DSOP_DOWNLEVEL_FILTER.Users |
                DSOP_DOWNLEVEL_FILTER.NetworkService |
                DSOP_DOWNLEVEL_FILTER.System |
                DSOP_DOWNLEVEL_FILTER.Service |
                DSOP_DOWNLEVEL_FILTER.LocalService |
                DSOP_DOWNLEVEL_FILTER.LocalGroups);

            scopeInitInfo[1].cbSize = size;
            scopeInitInfo[1].flType = (uint)(DSOP_SCOPE_TYPE.UplevelJoinedDomain | DSOP_SCOPE_TYPE.DownlevelJoinedDomain);
            scopeInitInfo[1].flScope = (uint)DSOP_SCOPE_FLAG.WantProviderWinNT;
            scopeInitInfo[1].FilterFlags.Uplevel.flNativeModeOnly = (uint)(DSOP_FILTER.Users | DSOP_FILTER.UniversalGroupsSE | DSOP_FILTER.GlobalGroupsSE | DSOP_FILTER.DomainLocalGroupsSE | DSOP_FILTER.Computers);
            scopeInitInfo[1].FilterFlags.Uplevel.flMixedModeOnly = (uint)(DSOP_FILTER.Users | DSOP_FILTER.GlobalGroupsSE | DSOP_FILTER.Computers);
            scopeInitInfo[1].FilterFlags.flDownlevel = (uint)(DSOP_DOWNLEVEL_FILTER.Users | DSOP_DOWNLEVEL_FILTER.GlobalGroups | DSOP_DOWNLEVEL_FILTER.Computers);

            scopeInitInfo[2].cbSize = size;
            scopeInitInfo[2].flType = (uint)DSOP_SCOPE_TYPE.EnterpriseDomain;
            scopeInitInfo[2].flScope = (uint)DSOP_SCOPE_FLAG.WantProviderWinNT;
            scopeInitInfo[2].FilterFlags.Uplevel.flNativeModeOnly = (uint)(DSOP_FILTER.Users | DSOP_FILTER.UniversalGroupsSE | DSOP_FILTER.GlobalGroupsSE | DSOP_FILTER.Computers);
            scopeInitInfo[2].FilterFlags.Uplevel.flMixedModeOnly = (uint)(DSOP_FILTER.Users | DSOP_FILTER.GlobalGroupsSE | DSOP_FILTER.Computers);

            scopeInitInfo[3].cbSize = size;
            scopeInitInfo[3].flType = (uint)(DSOP_SCOPE_TYPE.ExternalDownlevelDomain | DSOP_SCOPE_TYPE.ExternalUplevelDomain);
            scopeInitInfo[3].flScope = (uint)DSOP_SCOPE_FLAG.WantProviderWinNT;
            scopeInitInfo[3].FilterFlags.Uplevel.flNativeModeOnly = (uint)(DSOP_FILTER.UniversalGroupsSE | DSOP_FILTER.GlobalGroupsSE);
            scopeInitInfo[3].FilterFlags.Uplevel.flMixedModeOnly = (uint)(DSOP_FILTER.Users | DSOP_FILTER.GlobalGroupsSE);
            scopeInitInfo[3].FilterFlags.flDownlevel = (uint)(DSOP_DOWNLEVEL_FILTER.Users | DSOP_DOWNLEVEL_FILTER.LocalGroups | DSOP_DOWNLEVEL_FILTER.Computers);

            scopeInitInfo[4].cbSize = size;
            scopeInitInfo[4].flType = (uint)DSOP_SCOPE_TYPE.GlobalCatalog;
            scopeInitInfo[4].flScope = (uint)DSOP_SCOPE_FLAG.WantProviderWinNT;
            scopeInitInfo[4].FilterFlags.Uplevel.flNativeModeOnly = (uint)(DSOP_FILTER.Users | DSOP_FILTER.UniversalGroupsSE | DSOP_FILTER.GlobalGroupsSE | DSOP_FILTER.Computers);
        }

        private void ProcessSelection(IDataObject dataObject) {
            dialogResult = DialogResult.None;

            if (dataObject != null) {
                var bSTGMEDIUM = new STGMEDIUM {
                    tymed = 1,
                    hGlobal = IntPtr.Zero,
                    pUnkForRelease = null
                };

                var pFORMATETC = new FORMATETC {
                    cfFormat = DataFormats.GetFormat("CFSTR_DSOP_DS_SELECTION_LIST").Id,
                    ptd = IntPtr.Zero,
                    dwAspect = 1,
                    lindex = -1,
                    tymed = 1
                };

                if (dataObject.GetData(ref pFORMATETC, ref bSTGMEDIUM) == 0) {
                    var hMem = PInvoke.GlobalLock(bSTGMEDIUM.hGlobal);
                    if (hMem != IntPtr.Zero) {
                        var ptr = hMem;
                        try {
                            var count = Marshal.ReadInt32(ptr);
                            if (count > 0) {
                                ptr = (IntPtr)(((int)ptr) + (Marshal.SizeOf(typeof(uint)) * 2));
                                for (int i = 0; i < count; i++) {
                                    var ds_selection = (DS_SELECTION)Marshal.PtrToStructure(ptr, typeof(DS_SELECTION));
                                    adEntries.Add(new ADEntry {
                                        Class = ds_selection.pwzClass.ToLowerInvariant(),
                                        UPN = ds_selection.pwzADsPath
                                    });
                                }

                                dialogResult = DialogResult.OK;
                            }
                        }
                        finally {
                            PInvoke.GlobalUnlock(hMem);
                            if (bSTGMEDIUM.pUnkForRelease != null) {
                                Marshal.Release(bSTGMEDIUM.hGlobal);
                            }
                            else {
                                ptr = PInvoke.GlobalFree(bSTGMEDIUM.hGlobal);
                            }
                        }
                    }
                }
            }
        }

        public DialogResult ShowDialog(IWin32Window owner) {
            var objectPicker = new DSObjectPicker() as IDsObjectPicker;
            var scopeInititializeInfo = new DSOP_SCOPE_INIT_INFO[NUMBER_OF_SCOPES];
            var zero = IntPtr.Zero;

            Initialize(scopeInititializeInfo);

            var size = Marshal.SizeOf(typeof(DSOP_SCOPE_INIT_INFO));
            try {
                IDataObject selections;
                zero = Marshal.AllocHGlobal((int)(size * NUMBER_OF_SCOPES));
                for (var i = 0; i < NUMBER_OF_SCOPES; i++) {
                    Marshal.StructureToPtr(scopeInititializeInfo[i], (IntPtr)(((int)zero) + (i * size)), false);
                }

                var structure = new DSOP_INIT_INFO();
                structure.cbSize = (uint)Marshal.SizeOf(structure);
                structure.pwzTargetComputer = null;
                structure.cDsScopeInfos = NUMBER_OF_SCOPES;
                structure.aDsScopeInfos = zero;
                structure.flOptions = 0;
                structure.cAttributesToFetch = 0;

                objectPicker.Initialize(ref structure);
                objectPicker.InvokeDialog(owner.Handle, out selections);

                ProcessSelection(selections);
            }
            finally {
                for (int j = 0; j < NUMBER_OF_SCOPES; j++) {
                    Marshal.DestroyStructure((IntPtr)(((int)zero) + (j * size)), typeof(DSOP_SCOPE_INIT_INFO));
                }

                if (zero != IntPtr.Zero) {
                    Marshal.FreeHGlobal(zero);
                }
            }

            return this.dialogResult;
        }
    }
}